package org.luxor.commons.workflow.utils;

import com.google.common.collect.Lists;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.*;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.ActivitiObjectNotFoundException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.util.CollectionUtil;
import org.apache.commons.lang.StringUtils;
import org.luxor.commons.core.utils.SpringContextUtils;
import org.luxor.commons.workflow.constant.ExtensionVariableConst;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 流程辅助工具类
 *
 * @author Mr.yan  @date 2021/10/25
 */
public class ProcessUtils {
    private static int length = 8;
    private static final char[] CHAR_ARRAYS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".toCharArray();

    private static Map<String, Object> defaultProperties = new HashMap<>();

    static {
        defaultProperties.put(ExtensionVariableConst.FIRST_NODE_SKIP, true);
        defaultProperties.put(ExtensionVariableConst.USER_START_SKIP, false);
        defaultProperties.put(ExtensionVariableConst.SAME_USER_SKIP, false);
        defaultProperties.put(ExtensionVariableConst.USER_EMPTY_SKIP, false);
        defaultProperties.put(ExtensionVariableConst.ALL_SKIP, false);
    }

    public static String createActivityId() {
        StringBuilder sb = new StringBuilder("Activity_");
        Random random = new Random();
        for (int x = 0; x < length; ++x) {
            sb.append(CHAR_ARRAYS[random.nextInt(CHAR_ARRAYS.length)]);
        }
        return sb.toString();
    }

    public static String createFlowId() {
        StringBuilder sb = new StringBuilder("Flow_");
        Random random = new Random();
        for (int x = 0; x < length; ++x) {
            sb.append(CHAR_ARRAYS[random.nextInt(CHAR_ARRAYS.length)]);
        }
        return sb.toString();
    }

    /***
     * 获取BpmnModeL对象
     * @param processDefinitionId 流程定义ID
     * @return
     */
    public static BpmnModel getBpmnModel(String processDefinitionId) {
        if (processDefinitionId == null) {
            throw new ActivitiIllegalArgumentException("processDefinitionId is null");
        }
        RepositoryService repositoryService = SpringContextUtils.getBean(RepositoryService.class);
        return repositoryService.getBpmnModel(processDefinitionId);
    }

    /**
     * 获取流程的扩展配置
     *
     * @param processDefinitionId 流程定义ID
     * @return java.util.Map<java.lang.String, java.lang.String>
     */
    public static Map<String, Object> getProcessExtensionProperties(String processDefinitionId) {
        BpmnModel bpmnMode = getBpmnModel(processDefinitionId);
        Map<String, Object> processProperties = new HashMap<>();
        processProperties.putAll(defaultProperties);
        List<ExtensionElement> extensionElements = bpmnMode.getMainProcess().getExtensionElements().get("properties");
        for (ExtensionElement extensionElement : extensionElements) {
            if ("properties".equals(extensionElement.getName())) {
                List<ExtensionElement> properties = extensionElement.getChildElements().get("property");
                for (ExtensionElement property : properties) {
                    String name = property.getAttributeValue(null, "name");
                    String value = property.getAttributeValue(null, "value");
                    if (StringUtils.isNotBlank(name) && StringUtils.isNotEmpty(value)) {
                        processProperties.put(name, value);
                    }
                }
            }
        }
        return processProperties;
    }

    /**
     * 获取第一个人工节点
     *
     * @param processDefinitionId 流程定义ID
     * @return org.activiti.engine.impl.persistence.entity.TaskEntity
     */
    public static List<UserTask> findFirstUserTask(String processDefinitionId) {
        BpmnModel bpmnModel = getBpmnModel(processDefinitionId);

        List<StartEvent> processStartEvents = new ArrayList<>();
        List<StartEvent> findFlowElements = bpmnModel.getMainProcess().findFlowElementsOfType(StartEvent.class);
        for (StartEvent startEvent : findFlowElements) {
            if (startEvent.getParentContainer() instanceof Process) {
                processStartEvents.add(startEvent);
            }
        }
        if (CollectionUtil.isEmpty(processStartEvents)) {
            throw new ActivitiObjectNotFoundException("Cannot find StartEvent in ProcessDefinition. processDefinitionId: "
                    + processDefinitionId, EndEvent.class);
        }
        if (processStartEvents.size() > 1) {
            throw new ActivitiObjectNotFoundException("Not supported multi-StartEvent in ProcessDefinition. processDefinitionId:"
                    + processDefinitionId, EndEvent.class);
        }
        StartEvent startEvent = processStartEvents.get(0);

        List<UserTask> firstUserTasks = new ArrayList<>();
        List<SequenceFlow> outgoingFlows = startEvent.getOutgoingFlows();
        for (SequenceFlow sequenceFlow : outgoingFlows) {
            FlowElement targetFlow = sequenceFlow.getTargetFlowElement();
            if (targetFlow instanceof UserTask) {
                firstUserTasks.add((UserTask) targetFlow);
            }
        }
        return firstUserTasks;
    }

    /**
     * 获取上一步任务处理节点
     *
     * @param processDefinitionId 流程定义ID
     * @param activityId          当前活动节点
     * @return java.util.List<org.activiti.bpmn.model.Activity>
     */
    public static List<Activity> prevActivityList(String processDefinitionId, String activityId) {
        BpmnModel bpmnModel = getBpmnModel(processDefinitionId);
        if (bpmnModel == null) {
            throw new ActivitiObjectNotFoundException("Cannot find BpmnModel with processDefinitionId "
                    + processDefinitionId, BpmnModel.class);
        }
        FlowElement flowElement = bpmnModel.getFlowElement(activityId);
        if (flowElement == null) {
            throw new ActivitiException("未找到活动节点(" + activityId + ").");
        }
        if (!(flowElement instanceof Activity)) {
            throw new ActivitiException("节点(" + activityId + ")不是活动任务类型.");
        }
        List<Activity> prevActivityList = new ArrayList<>();

        FlowNode currentFlowNode = (FlowNode) flowElement;
        Queue<FlowNode> pendingFlowNodes = new LinkedList<>();
        while (currentFlowNode != null) {
            List<FlowElement> sourceFlowElements = currentFlowNode.getIncomingFlows().stream().map(SequenceFlow::getSourceFlowElement).collect(Collectors.toList());
            for (FlowElement sourceFlowElement : sourceFlowElements) {
                if (sourceFlowElement instanceof Gateway) {
                    pendingFlowNodes.offer((Gateway) sourceFlowElement);
                } else if (sourceFlowElement instanceof StartEvent && sourceFlowElement.getParentContainer() instanceof SubProcess) {
                    pendingFlowNodes.offer((SubProcess) sourceFlowElement.getParentContainer());
                } else if (sourceFlowElement instanceof SubProcess) {
                    Collection<FlowElement> subFlowElements = ((SubProcess) sourceFlowElement).getFlowElements();
                    List<FlowElement> subEndEvents = subFlowElements.stream().filter(el -> el instanceof EndEvent).collect(Collectors.toList());
                    for (FlowElement subEndEvent : subEndEvents) {
                        pendingFlowNodes.offer((EndEvent) subEndEvent);
                    }
                } else if (sourceFlowElement instanceof EndEvent) {
                    pendingFlowNodes.offer((EndEvent) sourceFlowElement);
                } else if (sourceFlowElement instanceof Activity) {
                    prevActivityList.add((Activity) sourceFlowElement);
                }
            }
            currentFlowNode = pendingFlowNodes.poll();
        }
        return prevActivityList;
    }

    /**
     * 获取下一步任务处理节点
     *
     * @param processDefinitionId 流程定义ID
     * @param activityId          当前活动节点
     * @return java.util.List<org.activiti.bpmn.model.Activity>
     */
    public static List<Activity> nextActivityList(String processDefinitionId, String activityId) {
        BpmnModel bpmnModel = getBpmnModel(processDefinitionId);
        if (bpmnModel == null) {
            throw new ActivitiObjectNotFoundException("Cannot find BpmnModel with processDefinitionId "
                    + processDefinitionId, BpmnModel.class);
        }
        FlowElement flowElement = bpmnModel.getFlowElement(activityId);
        if (flowElement == null) {
            throw new ActivitiException("未找到活动节点(" + activityId + ").");
        }
        if (!(flowElement instanceof Activity)) {
            throw new ActivitiException("节点(" + activityId + ")不是活动任务类型.");
        }
        List<Activity> nextActivityList = new ArrayList<>();

        FlowNode currentFlowNode = (FlowNode) flowElement;
        Queue<FlowNode> pendingFlowNodes = new LinkedList<>();
        while (currentFlowNode != null) {
            List<FlowElement> targetFlowElements = currentFlowNode.getOutgoingFlows().stream().map(SequenceFlow::getTargetFlowElement).collect(Collectors.toList());
            for (FlowElement targetFlowElement : targetFlowElements) {
                if (targetFlowElement instanceof Gateway) {
                    pendingFlowNodes.offer((Gateway) targetFlowElement);
                } else if (targetFlowElement instanceof SubProcess) {
                    Collection<FlowElement> subFlowElements = ((SubProcess) targetFlowElement).getFlowElements();
                    List<FlowElement> subStartEvents = subFlowElements.stream().filter(el -> el instanceof StartEvent).collect(Collectors.toList());
                    for (FlowElement subStartEvent : subStartEvents) {
                        pendingFlowNodes.offer((StartEvent) subStartEvent);
                    }
                } else if (targetFlowElement instanceof EndEvent) {
                    FlowElementsContainer parentContainer = targetFlowElement.getParentContainer();
                    if (parentContainer != null && parentContainer instanceof SubProcess) {
                        List<FlowElement> subProcessTargetFlowElements = ((SubProcess) parentContainer).getOutgoingFlows().stream()
                                .map(SequenceFlow::getTargetFlowElement).collect(Collectors.toList());
                        for (FlowElement subProcessTargetFlowElement : subProcessTargetFlowElements) {
                            if (subProcessTargetFlowElement instanceof Activity) {
                                nextActivityList.add((Activity) subProcessTargetFlowElement);
                            } else if (subProcessTargetFlowElement instanceof FlowNode) {
                                pendingFlowNodes.offer((FlowNode) subProcessTargetFlowElement);
                            }
                        }
                    }
                } else if (targetFlowElement instanceof Activity) {
                    nextActivityList.add((Activity) targetFlowElement);
                }
            }
            currentFlowNode = pendingFlowNodes.poll();
        }
        return nextActivityList;
    }

    /**
     * 获取全部子活动节点列表
     *
     * @param processDefinitionId 流程定义ID
     * @param activityId          当前活动节点
     * @return java.util.List<org.activiti.bpmn.model.Activity>
     */
    public static List<Activity> childActivityList(String processDefinitionId, String activityId) {
        BpmnModel bpmnModel = getBpmnModel(processDefinitionId);
        if (bpmnModel == null) {
            throw new ActivitiObjectNotFoundException("Cannot find BpmnModel with processDefinitionId "
                    + processDefinitionId, BpmnModel.class);
        }
        FlowElement flowElement = bpmnModel.getFlowElement(activityId);
        if (flowElement == null) {
            throw new ActivitiException("未找到活动节点(" + activityId + ").");
        }
        if (!(flowElement instanceof Activity)) {
            throw new ActivitiException("节点(" + activityId + ")不是活动任务类型.");
        }
        Set<Activity> childActivityList = childActivityList((FlowNode) flowElement);
        return Lists.newArrayList(childActivityList.iterator());
    }

    private static Set<Activity> childActivityList(FlowNode parentFlowNode) {
        Set<Activity> childActivityList = new HashSet<>();
        List<FlowElement> childFlowElements = new ArrayList<>();
        if (parentFlowNode instanceof SubProcess) {
            Collection<FlowElement> subFlowElements = ((SubProcess) parentFlowNode).getFlowElements();
            Collection<FlowElement> subStartEvents = subFlowElements.stream().filter(el -> el instanceof StartEvent).collect(Collectors.toList());
            for (FlowElement startEvent : subStartEvents) {
                childFlowElements.addAll(((StartEvent) startEvent).getOutgoingFlows().stream().map(SequenceFlow::getTargetFlowElement).collect(Collectors.toList()));
            }
        }
        childFlowElements.addAll(parentFlowNode.getOutgoingFlows().stream().map(SequenceFlow::getTargetFlowElement).collect(Collectors.toList()));
        for (FlowElement childFlowElement : childFlowElements) {
            if (childFlowElement instanceof Activity && !(childFlowElement instanceof SubProcess)) {
                childActivityList.add((Activity) childFlowElement);
            }
            if (childFlowElement instanceof FlowNode) {
                if (childFlowElement instanceof EndEvent && childFlowElement.getParentContainer() instanceof SubProcess) {
                    SubProcess outSubProcess = (SubProcess) childFlowElement.getParentContainer();
                    Collection<FlowElement> subProcessTargetFlowElements = outSubProcess.getOutgoingFlows().stream().map(SequenceFlow::getTargetFlowElement).collect(Collectors.toList());
                    for (FlowElement subProcessTargetFlowElement : subProcessTargetFlowElements) {
                        if (subProcessTargetFlowElement instanceof Activity && !(subProcessTargetFlowElement instanceof SubProcess)) {
                            childActivityList.add((Activity) subProcessTargetFlowElement);
                        }
                    }
                } else if (((FlowNode) childFlowElement).getOutgoingFlows().size() > 0) {
                    childActivityList.addAll(childActivityList((FlowNode) childFlowElement));
                }
            }
        }
        return childActivityList;
    }
}
